use super::{json, Base, MapSv, Resp, VMap, Value, VecF, VecFs, VecI};
use statrs::function::gamma::gamma;
use std::collections::HashMap;
use std::f64::consts::{E, PI};

pub trait CBase: Base {
    // 返回每组的子组不良数值，平均不良数值
    fn reject(&self, data: &VecI) -> (VecF, f64) {
        let mut ret = VecF::new();
        let mut p = 0.0;

        for f in data.iter() {
            let f = *f as f64;
            p += f;
            ret.push(f);
        }

        p /= ret.len() as f64;

        (ret, p)
    }

    fn get_tab_pu(&self, para: &MapSv) -> (String, String) {
        let tab = match para.k_str_empty("tab") {
            Some(v) => v,
            None => "stnstatus",
        };

        let obj = match para.k_str_empty("obj") {
            Some(v) => v,
            None => "state",
        };

        (tab.to_string(), obj.to_string())
    }

    fn get_data_pu(
        &self,
        body: &Value,
        sub: i64,
        tab: &str,
        obj: &str,
    ) -> Result<(VecI, VecI), Value> {
        let body = body.ck_obj(tab)?.ck_array("data")?;

        let mut total = Vec::new();
        let mut reject = Vec::new();

        let mut n = 0;
        let mut rj = 0;

        for d in body.iter() {
            let state = d.ck_i64(obj)?;
            if state != 1 {
                rj += 1;
            }

            n += 1;
            if n >= sub {
                total.push(n);
                reject.push(rj);

                n = 0;
                rj = 0;
            }
        }

        Ok((total, reject))
    }

    fn data_pu_cmd(&self, para: &MapSv, tab: &str, obj: &str) -> Resp {
        let line = para.ck_str_empty("line")?;
        let wsn = para.ck_str_empty("wsn")?;
        let start = para.ck_str_empty("start")?;
        let end = para.ck_str_empty("end")?;

        Ok(json!({
            "n": tab,
            "obj": obj,
            "opt": {
                "and": [
                    {"eq": "line", "v": line},
                    {"eq": "wsn", "v": wsn},
                    {"ge": "time", "v": start, "utc": 8},
                    {"le": "time", "v": end, "utc": 8},
                ]
            }
        }))
    }

    // 返回每组的不良率，和平均不良率
    fn reject_pu(&self, da_total: &VecI, da_reject: &VecI) -> (VecF, f64) {
        let length = da_total.len();

        let mut ret = VecF::new();
        let mut total = 0.0;
        let mut worse = 0.0;

        for i in 0..length {
            let t = da_total[i] as f64;
            let w = da_reject[i] as f64;

            total += t;
            worse += w;

            let v = w / t;
            ret.push(v);
        }

        let cl = worse / total;

        (ret, cl)
    }

    // 返回每组的子组不良数值，平均不良数值,和平均不良率
    fn reject_npc(&self, group: i64, da_reject: &VecI) -> (VecF, f64, f64) {
        let mut ret = VecF::new();
        let mut total = 0;
        let mut worse = 0;

        for w in da_reject.iter() {
            total += group;
            worse += w;

            ret.push(*w as f64);
        }

        let worse = worse as f64;
        let p = worse / (ret.len() as f64);
        let cl = worse / (total as f64);

        (ret, p, cl)
    }

    fn data_xbar_cmd(&self, tab: &str, obj: &str, para: &MapSv) -> Resp {
        let line = para.ck_str_empty("line")?;
        let wsn = para.ck_str_empty("wsn")?;
        let start = para.ck_str_empty("start")?;
        let end = para.ck_str_empty("end")?;

        let opt = match para.k_val("opt") {
            Some(v) => v.clone(),
            None => json!({
                "and": [
                    {"eq": "line", "v": line},
                    {"eq": "wsn", "v": wsn},
                    {"ge": "time", "v": start, "utc": 8},
                    {"le": "time", "v": end, "utc": 8},
                ]
            }),
        };

        Ok(json!({
            "n": tab,
            "obj": obj,
            "opt": opt
        }))
    }

    /*
    A2:     X-R图, 均值X图, 控制限系数
    d2:     X-R图, 极差R图, 标准差估计值除数
    D3:     X-R图, 极差R图, 控制限系数
    D4:     X-R图, 极差R图, 控制限系数

    A3:     X-R图, 极差R图, 控制限系数
    C4:     X-R图, 极差R图, 标准差估计值除数
    B3:     X-R图, 极差R图, 控制限系数
    B4:     X-R图, 极差R图, 控制限系数

    子组容量最小为2, 对应于数组第一行元素
    */
    fn get_value(&self, row: i64, key: &str) -> f64 {
        let row = row as usize;

        let col = match key {
            "A2" => 0,
            "d2" => 1,
            "D3" => 2,
            "D4" => 3,
            "A3" => 4,
            "C4" => 5,
            "B3" => 6,
            "B4" => 7,
            _ => 0,
        };

        let xbar_table = [
            [1.88, 1.128, 0.0, 3.267, 2.659, 0.7979, 0.0, 3.267],
            [1.023, 1.693, 0.0, 2.574, 1.954, 0.8862, 0.0, 2.568],
            [0.729, 2.059, 0.0, 2.282, 1.628, 0.9213, 0.0, 2.266],
            [0.577, 2.326, 0.0, 2.114, 1.427, 0.94, 0.0, 2.089],
            [0.483, 2.534, 0.0, 2.004, 1.287, 0.9515, 0.03, 1.97],
            [0.149, 2.704, 0.076, 1.924, 1.182, 0.9594, 0.118, 1.882],
            [0.373, 2.847, 0.136, 1.864, 1.099, 0.965, 0.185, 1.815],
            [0.337, 2.97, 0.184, 1.816, 1.032, 0.9693, 0.239, 1.761],
            [0.308, 3.078, 0.223, 1.777, 0.975, 0.9727, 0.284, 1.716],
            [0.285, 3.173, 0.256, 1.744, 0.927, 0.9754, 0.321, 1.679],
            [0.266, 3.258, 0.283, 1.717, 0.886, 0.9776, 0.354, 1.646],
            [0.249, 3.336, 0.307, 1.693, 0.85, 0.9794, 0.382, 1.618],
            [0.235, 3.407, 0.328, 1.672, 0.817, 0.981, 0.406, 1.594],
            [0.223, 3.472, 0.347, 1.653, 0.789, 0.9823, 0.428, 1.572],
            [0.212, 3.532, 0.363, 1.637, 0.763, 0.9835, 0.448, 1.552],
            [0.203, 3.588, 0.378, 1.622, 0.739, 0.9845, 0.466, 1.534],
            [0.194, 3.64, 0.391, 1.608, 0.718, 0.9854, 0.482, 1.518],
            [0.187, 3.689, 0.403, 1.597, 0.698, 0.9862, 0.497, 1.503],
            [0.18, 3.735, 0.415, 1.585, 0.68, 0.9869, 0.51, 1.49],
            [0.173, 3.778, 0.425, 1.575, 0.663, 0.9876, 0.523, 1.477],
            [0.167, 3.819, 0.434, 1.566, 0.647, 0.9882, 0.534, 1.466],
            [0.162, 3.858, 0.443, 1.557, 0.633, 0.9887, 0.545, 1.455],
            [0.157, 3.895, 0.451, 1.548, 0.619, 0.9892, 0.555, 1.445],
            [0.153, 3.931, 0.459, 1.541, 0.606, 0.9896, 0.565, 1.435],
        ];

        xbar_table[row - 2][col]
    }

    fn get_mean(&self, n: f64, data: &VecF) -> Option<(f64, f64, f64)> {
        let mut it = data.iter();

        let f = it.next()?;

        let mut min = *f;
        let mut max = *f;
        let mut sum = *f;

        for d in it {
            min = min.min(*d);
            max = max.max(*d);
            sum += *d;
        }

        let mean = sum / n;

        Some((min, max, mean))
    }

    fn get_std(
        &self,
        len: usize,
        mean: f64,
        min: f64,
        max: f64,
        data: &mut VecF,
        row: i64,
        total: i64,
        sp: Option<f64>,
    ) -> Option<HashMap<String, f64>> {
        let n = len as f64;

        let mut var = 0.0;
        let mut skew = 0.0;
        let mut kurt = 0.0;

        for d in data.iter() {
            let v = d - mean;

            let v2 = v * v;
            let v3 = v2 * v;
            let v4 = v3 * v;

            var += v2;
            skew += v3;
            kurt += v4;
        }

        var /= n - 1.0;

        let std = var.sqrt();

        let d = total * (row - 1);
        let d = d as f64;
        let c4 = (2.0 / d).sqrt() * gamma((d + 1.0) / 2.0) / gamma(d / 2.0);
        let gstd = match sp {
            Some(v) => v / c4,
            None => std * n / (n - 1.0),
        };

        skew /= std.powf(3.0);
        skew *= n / ((n - 1.0) * (n - 2.0));

        kurt /= std.powf(4.0);
        kurt *= n * (n + 1.0) / ((n - 1.0) * (n - 2.0) * (n - 3.0));
        kurt -= 3.0 * (n - 1.0).powf(2.0) / ((n - 2.0) * (n - 3.0));

        let cv = std / mean;

        let mut sta = HashMap::new();

        sta.insert("min".to_string(), min);
        sta.insert("max".to_string(), max);
        sta.insert("mean".to_string(), mean);
        sta.insert("var".to_string(), var);
        sta.insert("std".to_string(), std);
        sta.insert("gstd".to_string(), gstd);
        sta.insert("skew".to_string(), skew);
        sta.insert("kurt".to_string(), kurt);
        sta.insert("cv".to_string(), cv);
        sta.insert("ext".to_string(), max - min);

        if let None = sp {
            return Some(sta);
        }

        data.sort_unstable_by(|a, b| a.total_cmp(b));

        let mid = if len % 2 == 0 {
            let p = len / 2;
            let a = data.get(p)?;
            let b = data.get(p - 1)?;
            (a + b) / 2.0
        } else {
            let p = len / 2;
            let d = data.get(p)?;
            *d
        };

        let p = (n * 0.05).round() as usize;
        let per5 = data.get(p)?;

        let p = (n * 0.25).round() as usize;
        let per25 = data.get(p)?;

        let p = (n * 0.75).round() as usize;
        let per75 = data.get(p)?;

        let p = (n * 0.95).round() as usize;
        let per95 = data.get(p)?;

        sta.insert("mid".to_string(), mid);
        sta.insert("per5".to_string(), *per5);
        sta.insert("per25".to_string(), *per25);
        sta.insert("per75".to_string(), *per75);
        sta.insert("per95".to_string(), *per95);

        Some(sta)
    }

    // ({
    //   norm: [[a, b]],  正态分布
    //   gnorm: [[a, b]], 组内正态分布
    //   bar: [[a, b]],   直方图
    // }, {
    //     mean: 2,  平均值
    //     max: 2,  最大值
    //     min: 2,  最小值
    //     mid: 2,  中位数
    //     var: 2,  方差
    //     std: 2,  标准差
    //     gstd: 2,  组内标准差
    //     skew: 2,  偏度
    //     kurt: 2,  峰度
    //     cv: 2,  变异系数
    //     ext: 2,  极差
    //     per5: 2,  5%分位
    //     per25: 2,  q1分位
    //     per75: 2,  q3%分位
    //     per95: 2, 95%分位
    // })
    fn do_normal(
        &self,
        data: &mut VecF,
        row: i64,
        total: i64,
        sp: Option<f64>,
    ) -> Option<(Value, HashMap<String, f64>)> {
        let len = data.len();
        let n = len as f64;

        let (min, max, mean) = self.get_mean(n, data)?;

        let sta = self.get_std(len, mean, min, max, data, row, total, sp)?;

        let step = (max - min) / n;

        let mut norm = Vec::new();
        let mut gnorm = Vec::new();

        let std = sta.get("std")?;
        let gstd = sta.get("gstd")?;

        let mut d = mean - 3.0 * std;
        let right = mean + 3.0 * std;

        let mut n_max: f64 = 0.0;

        loop {
            if d > right {
                break;
            }

            let x = d - mean;

            let s = std * std;
            let tmp = (1.0 / (2.0 * PI * s).sqrt()) * E.powf(-x * x / (2.0 * s));
            norm.push(vec![d, tmp]);

            n_max = n_max.max(tmp);

            let s = gstd * gstd;
            let tmp = (1.0 / (2.0 * PI * s).sqrt()) * E.powf(-x * x / (2.0 * s));
            gnorm.push(vec![d, tmp]);

            d += step;
        }

        let (bar, ymax) = self.calc_bar(data, mean, std, max, min);

        let ny = ymax / n_max;

        Some((
            json!({
                "norm": norm,
                "gnorm": gnorm,
                "bar": bar,
                "mul": ny,
            }),
            sta,
        ))
    }

    fn calc_bar(&self, data: &VecF, mean: f64, std: &f64, max: f64, min: f64) -> (VecFs, f64) {
        let len = data.len();

        let mut m = [0; 8];
        let mut m_c = [mean - 3.0 * std; 7];

        for i in 1..7 {
            m_c[i] += std * i as f64;
        }

        let gn = if len < 100 {
            5
        } else if len >= 100 && len < 200 {
            6
        } else if len >= 200 && len < 500 {
            7
        } else if len >= 500 && len < 1000 {
            8
        } else if len >= 1000 && len < 2000 {
            9
        } else if len >= 2000 && len < 5000 {
            10
        } else if len >= 5000 && len < 10000 {
            11
        } else {
            12
        };

        let mut group = Vec::new();
        let mut group_c = Vec::new();

        let w = (max - min) / gn as f64;
        let mut step = min;

        for _ in 0..gn {
            group.push(step);
            group_c.push(0);
            step += w;
        }

        for d in data.iter() {
            let d = *d;

            for i in 1..gn {
                if d >= group[i - 1] && d < group[i] {
                    group_c[i - 1] += 1;
                }
            }

            if d <= m_c[0] {
                m[0] += 1;
            } else if d > m_c[m_c.len() - 1] {
                m[6] += 1;
            } else {
                for i in 1..7 {
                    if d > m_c[i - 1] && d <= m_c[i] {
                        m[i] += 1;
                    }
                }
            }
        } // for

        let mut bar = Vec::new();

        let f = group_c[0] as f64;
        let mut y_max = f;

        for i in 0..gn {
            let tmp = group_c[i] as f64;
            y_max = y_max.max(tmp);

            bar.push(vec![group[i], tmp]);
        }

        (bar, y_max)
    }

    fn get_sl(&self, data: &MapSv) -> Option<(f64, f64, f64)> {
        let usl = data.k_f64("usl")?;
        let lsl = data.k_f64("lsl")?;
        let sl = match data.k_f64("sl") {
            Some(v) => v,
            None => (usl + lsl) / 2.0,
        };

        Some((usl, lsl, sl))
    }

    fn calc_cpk(&self, data: &MapSv, sta: &HashMap<String, f64>) -> Option<Value> {
        if let Some((usl, lsl, sl)) = self.get_sl(data) {
            let std = sta.get("std")?;
            let gstd = sta.get("gstd")?;
            let mean = sta.get("mean")?;

            let pp = (usl - lsl) / (6.0 * std);
            let ppu = (usl - mean) / (3.0 * std);
            let ppl = (mean - lsl) / (3.0 * std);
            let ppk = ppu.min(ppl);
            let pr = 1.0 / pp;

            let ca = (mean - sl) / (usl - sl).max(sl - lsl);
            let cp = (usl - lsl) / (6.0 * gstd);
            let cpu = (usl - mean) / (3.0 * gstd);
            let cpl = (mean - lsl) / (3.0 * gstd);
            let cpk = cpu.min(cpl);

            let p = json!({
                "pp": pp,
                "ppu": ppu,
                "ppl": ppl,
                "ppk": ppk,
                "pr": pr,
                "ca": ca,
                "cp": cp,
                "cpu": cpu,
                "cpl": cpl,
                "cpk": cpk,
                "usl": usl,
                "sl": sl,
                "lsl": lsl,
            });

            Some(p)
        } else {
            None
        }
    }
}
